/*
 * jQuery Scrollbars
 * 
 * Copyright (c) 2010 Sam Holmes
 * Dual licensed under the MIT and Creative Commons 3.0 BY-SA licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://creativecommons.org/licenses/by-sa/3.0/
 */

(function($)
{
	function createElementWithClass(name, className)
	{
		var ele = document.createElement(name);
		ele.className = className;
		return ele;
	}
	function toNumber(s)
	{
		var r; 
		return isNaN(r = parseInt(s)) ? 0 : r;
	}
	
	var activeScrollbar, activeContent, 
		activeYDirection, activeXDirection,
		startedDragY, startedTabY,
		startedDragX, startedTabX,
		loopIntervalId, alignFunctions = [];
	
	function loopCallback()
	{
		for (var i = 0, len = alignFunctions.length; i < len; ++i)
		{
			alignFunctions[i]();
		}
		
		// Scroll in a vertical direction
		if (activeYDirection)
			activeContent.scrollTop += activeContent.clientHeight * .03 * activeYDirection;
		// Scroll in a horizontal direction
		if (activeXDirection)
			activeContent.scrollLeft += activeContent.clientWidth * .03 * activeXDirection;
	}
	
	// Set document events
	$(document)
		.mousemove(function mm(event)
		{
			if (activeScrollbar)
			{
				var $content = $(activeContent),
					$scrollbar = $(activeScrollbar);
				
				if (activeScrollbar.upArrow)
				{
					var contentHeight = $content.height()+
						toNumber($content.css('borderTopWidth'))+
						toNumber($content.css('borderBottomWidth'))+
						toNumber($content.css('paddingTop'))+
						toNumber($content.css('paddingBottom'));  
					var trackYMargins = toNumber($(activeScrollbar.track).css('marginTop')) + toNumber($(activeScrollbar.track).css('marginBottom'));
					var trackHeight = contentHeight - trackYMargins;
					var tabYMargins = toNumber($(activeScrollbar.tab).css('marginTop')) + toNumber($(activeScrollbar.tab).css('marginBottom'));
					var tabHeight = trackHeight * (activeContent.clientHeight / activeContent.scrollHeight) - tabYMargins | 0; // The | 0 part is to turn it into an integer
						if (tabHeight < 0) tabHeight = 0;
					
					var yLimit = trackHeight - tabHeight - tabYMargins;
					var yPos = (event.clientY - startedDragY) + startedTabY;
					
					activeContent.scrollTop = (activeContent.scrollHeight - activeContent.clientHeight) * (yPos / yLimit);
				}
				if (activeScrollbar.leftArrow)
				{
					var contentWidth = $content.width()+
						toNumber($content.css('borderLeftWidth'))+
						toNumber($content.css('borderRightWidth'))+
						toNumber($content.css('paddingLeft'))+
						toNumber($content.css('paddingRight'));  
					var trackXMargins = toNumber($(activeScrollbar.track).css('marginLeft')) + toNumber($(activeScrollbar.track).css('marginRight'));
					var trackWidth = contentWidth - trackXMargins;
					var tabXMargins = toNumber($(activeScrollbar.tab).css('marginLeft')) + toNumber($(activeScrollbar.tab).css('marginRight'));
					var tabWidth = trackWidth * (activeContent.clientWidth / activeContent.scrollWidth) - tabXMargins | 0; // The | 0 part is to turn it into an integer
						if (tabWidth < 0) tabWidth = 0;
					
					var xLimit = trackWidth - tabWidth - tabXMargins;
					var xPos = (event.clientX - startedDragX) + startedTabX;
					
					activeContent.scrollLeft = (activeContent.scrollWidth - activeContent.clientWidth) * (xPos / xLimit);
				}
				return false;
			}
		})
		.mouseup(function()
		{
			$('.scrollbar-vertical .tab-active').removeClass('tab-active');
			$('.scrollbar-vertical .arrow-up-active').removeClass('arrow-up-active');
			$('.scrollbar-vertical .arrow-down-active').removeClass('arrow-down-active');
			$('.scrollbar-horizontal .tab-active').removeClass('tab-active');
			$('.scrollbar-horizontal .arrow-left-active').removeClass('arrow-left-active');
			$('.scrollbar-horizontal .arrow-right-active').removeClass('arrow-right-active');
			activeYDirection = 0;
			activeXDirection = 0;
			activeScrollbar = null;
		});
		
	$.fn.scrollbars = function(options)
	{
		// Set default options
		options = options || {};
		options = {
			classSuffix: options.classSuffix || '',
			overflowX: options.overflowX == 'scroll' || options.overflowX == 'hidden' 
				? options.overflowX 
				: options.overflow == 'scroll' || options.overflow == 'hidden' 
					? options.overflow 
					:'auto',
			overflowY: options.overflowY == 'scroll' || options.overflowY == 'hidden' 
				? options.overflowY
				: options.overflow == 'scroll' || options.overflow == 'hidden' 
					? options.overflow 
					:'auto',
			stealth: options.stealth || false
		};
		
		// Set up a main loop if it's not already set up
		if (loopIntervalId == undefined)
			loopIntervalId = setInterval(loopCallback, 30);
		
		// Create a scrollbar for each elements queried
		this.each(function()
		{
			if (!this.hasCustomScrollbars) //If the element does not already have scrollbars
			{
				var content = this,
					$content = $(content);
				var scrollbarY = createElementWithClass('div', 'scrollbar-vertical'+(options.classSuffix ? '-'+options.classSuffix : ''));
					scrollbarY.track = createElementWithClass('div', 'track');
					scrollbarY.trackTop = createElementWithClass('div', 'top');
					scrollbarY.trackBottom = createElementWithClass('div', 'bottom');
					scrollbarY.upArrow = createElementWithClass('div', 'arrow-up');
					scrollbarY.downArrow = createElementWithClass('div', 'arrow-down');
					scrollbarY.tab = createElementWithClass('div', 'tab');
					scrollbarY.tabTop = createElementWithClass('div', 'top');
					scrollbarY.tabBottom = createElementWithClass('div', 'bottom');
				var scrollbarX = createElementWithClass('div', 'scrollbar-horizontal'+(options.classSuffix ? '-'+options.classSuffix : ''));
					scrollbarX.track = createElementWithClass('div', 'track');
					scrollbarX.trackLeft = createElementWithClass('div', 'left');
					scrollbarX.trackRight = createElementWithClass('div', 'right');
					scrollbarX.leftArrow = createElementWithClass('div', 'arrow-left');
					scrollbarX.rightArrow = createElementWithClass('div', 'arrow-right');
					scrollbarX.tab = createElementWithClass('div', 'tab');
					scrollbarX.tabLeft = createElementWithClass('div', 'left');
					scrollbarX.tabRight = createElementWithClass('div', 'right');
				
				// Append scrollbarY elements to each other and attach event handles  
				$(scrollbarY)
					.append(scrollbarY.track)
					.append(scrollbarY.upArrow)
					.append(scrollbarY.downArrow);
				if (options.stealth)
				{
					$(scrollbarY)
						.mouseenter(function()
						{
							currentHoveredOverElement = this;
						})
						.mouseleave(function()
						{
							currentHoveredOverElement = null;
						});
				}
				$(scrollbarY.track)
					.append(scrollbarY.tab)
					.append(scrollbarY.trackTop)
					.append(scrollbarY.trackBottom);
				$(scrollbarY.tab)
					.append(scrollbarY.tabTop)
					.append(scrollbarY.tabBottom)
					.mouseenter(function()
					{
						$(this).addClass('tab-hover');
					})
					.mouseleave(function()
					{
						$(this).removeClass('tab-hover');
					})
					.mousedown(function(event)
					{
						$(this).addClass('tab-active');
						activeContent = content;
						activeScrollbar = scrollbarY;
						startedDragY = event.clientY;
						startedTabY = toNumber($(this).css('top'));
						return false;
					});
				$(scrollbarY.upArrow)
					.mouseenter(function()
					{
						$(this).addClass('arrow-up-hover');
					})
					.mouseleave(function()
					{
						$(this).removeClass('arrow-up-hover');
					})
					.mousedown(function()
					{
						$(this).addClass('arrow-up-active');
						activeYDirection = -1;
						activeContent = content;
						return false;
					});
				$(scrollbarY.downArrow)
					.mouseenter(function()
					{
						$(this).addClass('arrow-down-hover');
					})
					.mouseleave(function()
					{
						$(this).removeClass('arrow-down-hover');
					})
					.mousedown(function()
					{
						$(this).addClass('arrow-down-active');
						activeYDirection = 1;
						activeContent = content;
						return false;
					});
				
				// Append scrollbarX elements to each other and attach event handles  
				$(scrollbarX)
					.append(scrollbarX.track)
					.append(scrollbarX.leftArrow)
					.append(scrollbarX.rightArrow);
				if (options.stealth)
				{
					$(scrollbarX)
						.mouseenter(function()
						{
							currentHoveredOverElement = this;
						})
						.mouseleave(function()
						{
							currentHoveredOverElement = null;
						});
				}
				$(scrollbarX.track)
					.append(scrollbarX.tab)
					.append(scrollbarX.trackLeft)
					.append(scrollbarX.trackRight);
				$(scrollbarX.tab)
					.append(scrollbarX.tabLeft)
					.append(scrollbarX.tabRight)
					.mouseenter(function()
					{
						$(this).addClass('tab-hover');
					})
					.mouseleave(function()
					{
						$(this).removeClass('tab-hover');
					})
					.mousedown(function(event)
					{
						$(this).addClass('tab-active');
						activeContent = content;
						activeScrollbar = scrollbarX;
						startedDragX = event.clientX;
						startedTabX = toNumber($(this).css('left'));
						return false;
					});
				$(scrollbarX.leftArrow)
					.mouseenter(function()
					{
						$(this).addClass('arrow-left-hover');
					})
					.mouseleave(function()
					{
						$(this).removeClass('arrow-left-hover');
					})
					.mousedown(function()
					{
						$(this).addClass('arrow-left-active');
						activeXDirection = -1;
						activeContent = content;
						return false;
					});
				$(scrollbarX.rightArrow)
					.mouseenter(function()
					{
						$(this).addClass('arrow-right-hover');
					})
					.mouseleave(function()
					{
						$(this).removeClass('arrow-right-hover');
					})
					.mousedown(function()
					{
						$(this).addClass('arrow-right-active');
						activeXDirection = 1;
						activeContent = content;
						return false;
					});
				// Hide the default scroll bars and append the new ones
				$content.css({
					overflow: 'hidden'
				}).after(scrollbarY).after(scrollbarX);
				
				//Stealth option
				if (options.stealth)
				{
					$content
						.mouseenter(function()
						{
							currentHoveredOverElement = this;
						})
						.mouseleave(function()
						{
							currentHoveredOverElement = null;
						});
				}
				
				
				var lastContentWidth, lastContentHeight,
					lastContentTop, lastContentLeft,
					lastScrollHeight, lastScrollWidth,
					lastScrollTop, lastScrollLeft,
					currentHoveredOverElement = null, lastHoveredOverElement;
				
				function alignScrollbars()
				{
					/* IMPORTANT NOTES
					 * I know this function might look messy, but it's actually very precise.
					 * All the separate if statements are purposefully designed in such a way where
					 * code is only executed when necessary. Only when the content's dimensions,
					 * scroll or position is changed will correct scrollbar alignment code be executed.
					 * BE CAREFUL when attempting to re-organize the logical flow of this function.
					 * 
					 * Sense every pair of vertical and horizontal scrollbars has one of these 
					 * functions, this function needs to take the least amount of load. The quicker
					 * this function can finish the quicker we can get to the next call to one of these functions.
					 * 
					 * There is room for optimization improvements, although, it's complicated work. I know that I 
					 * need to only align scroll bars when they're visible (not hidden), but when they become visible
					 * there is nothing to trigger their re-alignment, until someone scrolls or something.
					 */
					var contentOffset = $content.offset();
					var contentTop = contentOffset.top;
					var contentLeft = contentOffset.left;
					var contentWidth = $content.width()+
						toNumber($content.css('borderLeftWidth'))+
						toNumber($content.css('borderRightWidth'))+
						toNumber($content.css('paddingLeft'))+
						toNumber($content.css('paddingRight'));
					var contentHeight = $content.height()+
						toNumber($content.css('borderTopWidth'))+
						toNumber($content.css('borderBottomWidth'))+
						toNumber($content.css('paddingTop'))+
						toNumber($content.css('paddingBottom'));
					// Variables that hold conditions for if statements
					var contentDimensionsChanged = lastContentHeight != contentHeight || lastContentWidth != contentWidth,
						contentOffsetChanged = lastContentTop != contentTop || lastContentLeft != contentLeft,
						contentScrollYChanged = lastScrollTop != content.scrollTop || lastScrollHeight != content.scrollHeight,
						contentScrollXChanged = lastScrollLeft != content.scrollLeft || lastScrollWidth != content.scrollWidth,
						hoveredOverElementChanged = lastHoveredOverElement !== currentHoveredOverElement;
					
					if (contentDimensionsChanged || contentOffsetChanged
						|| contentScrollYChanged || contentScrollXChanged
						|| hoveredOverElementChanged)
					{
						var trackYMargins = toNumber($(scrollbarY.track).css('marginTop'))+
							toNumber($(scrollbarY.track).css('marginBottom'));
						var trackXMargins = toNumber($(scrollbarX.track).css('marginLeft'))+
							toNumber($(scrollbarX.track).css('marginRight'));
						var trackHeight = contentHeight - trackYMargins;
						var trackWidth = contentWidth - trackXMargins;
						
						var tabYMargins = toNumber($(scrollbarY.tab).css('marginTop'))+
							toNumber($(scrollbarY.tab).css('marginBottom'));
						var tabXMargins = toNumber($(scrollbarX.tab).css('marginLeft'))+
							toNumber($(scrollbarX.tab).css('marginRight'));
						var tabHeight = trackHeight * (content.clientHeight / content.scrollHeight) - tabYMargins | 0; // The | 0 part is to turn it into an integer
							if (tabHeight < 0) tabHeight = 0;
						var tabWidth = trackWidth * (content.clientWidth / content.scrollWidth) - tabXMargins | 0; // The | 0 part is to turn it into an integer
							if (tabWidth < 0) tabWidth = 0;
						
						// Set scrollTop to it's limits if it exceeds it's limits
						if (content.scrollTop > content.scrollHeight - content.clientHeight)
							content.scrollTop = content.scrollHeight;
						
						
						// Hide when overflow is hidden or track is too small
						// Or when overflow is auto and tab has no scroll
						if (options.overflowY == 'hidden' || trackHeight <= tabYMargins
							|| (content.scrollHeight - content.clientHeight == 0 && options.overflowY == 'auto'))
							scrollbarY.style.display = 'none';
						//Else, if in stealth
						else if (options.stealth && hoveredOverElementChanged)
						{
							if (currentHoveredOverElement == content)
							{
								$(scrollbarY).stop().css({display:''}).fadeTo('fast', 1);
							}
							else
							{
								if (currentHoveredOverElement == scrollbarY)
									$(scrollbarY).stop().fadeTo('fast', 1);
								else
									$(scrollbarY).stop().fadeTo('slow', 0, function(){
										this.style.display = 'none';
									});
							}
						}
						// Else, show if hidden
						else if ((scrollbarY.style.display == 'none' && !options.stealth) || (options.stealth && currentHoveredOverElement == content))
							scrollbarY.style.display = '';
						
						
						// Hide when overflow is hidden or track is too small
						// Or when overflow is auto and tab has no scroll
						if (options.overflowX == 'hidden' || trackWidth <= tabXMargins
							|| (content.scrollWidth - content.clientWidth == 0 && options.overflowX == 'auto'))
							scrollbarX.style.display = 'none';
						//Else, if in stealth
						else if (options.stealth && hoveredOverElementChanged)
						{
							if (currentHoveredOverElement == content)
							{
								$(scrollbarX).stop().css({display:''}).fadeTo('fast', 1);
							}
							else
							{
								if (currentHoveredOverElement == scrollbarX)
									$(scrollbarX).stop().fadeTo('fast', 1);
								else
									$(scrollbarX).stop().fadeTo('slow', 0, function(){
										this.style.display = 'none';
									});
							}
						}
						// Else, show if hidden
						else if ((scrollbarX.style.display == 'none' && !options.stealth) || (options.stealth && currentHoveredOverElement == content))
							scrollbarX.style.display = '';
						
						
						lastHoveredOverElement = currentHoveredOverElement;
					}
					
					// Vertical Scrollbar
					if (contentDimensionsChanged || contentOffsetChanged)
					{
						// Align scrollbarY
						$(scrollbarY)
							.css({
								top: contentTop,
								left: contentLeft + contentWidth
							})
							.height(contentHeight);
						// Align scrollbarY track
						$(scrollbarY.track)
							.height(trackHeight);
					}
					if (contentDimensionsChanged || contentScrollYChanged)
					{
						// Align scrollbarY tab
						var yLimit = trackHeight - tabHeight - tabYMargins;
						var yPos = content.scrollTop / (content.scrollHeight - content.clientHeight);
						if (yPos > 1) yPos = 1;
						$(scrollbarY.tab)
							.css('top', yLimit * yPos)
							.height(tabHeight);
						
						lastScrollTop = content.scrollTop;
						lastScrollHeight = content.scrollHeight;
					}
					
					// Horizontal Scrollbar
					if (contentDimensionsChanged || contentOffsetChanged)
					{
						// Align scrollbarX
						$(scrollbarX)
							.css({
								top: contentTop + contentHeight,
								left: contentLeft
							})
							.width(contentWidth);
						// Align scrollbarX track
						$(scrollbarX.track)
							.width(trackWidth);
						
						lastContentTop = contentOffset.top;
						lastContentLeft = contentOffset.left;
					}
					if (contentDimensionsChanged || contentScrollXChanged)
					{
						// Align scrollbarX tab
						var xLimit = trackWidth - tabWidth - tabXMargins;
						var xPos = content.scrollLeft / (content.scrollWidth - content.clientWidth);
						if (xPos > 1) xPos = 1;
						$(scrollbarX.tab)
							.css('left', xLimit * xPos)
							.width(tabWidth);
						
						/*
						 * I wanted to put these in an if statement so that the function completes faster.
						 * However, these need to be in the last if statement closest to the bottom of the function.
						 */ 
						lastScrollLeft = content.scrollLeft;
						lastScrollWidth = content.scrollWidth;
						lastContentWidth = contentWidth;
						lastContentHeight = contentHeight;
					}
				}
				// Add the alignScrollbars function to the alignFunctions array
				alignFunctions.push(alignScrollbars);
				
				// This will be used to denote that the content has scrollbars
				content.hasCustomScrollbars = true;
			}
		});
	};
})(jQuery);